Workshop Swagger et OpenAPI

swagger logo

Philippe Bousquet <pbousquet@sqli.com>

Introduction

Aujourd’hui les APIs ont envahie le monde…​

Qu’est ce qu’une API ?

Une API (pour Application Programming Interface) est un ensemble normalisé de classes, de méthodes, de fonctions et de constantes qui sert de façade par laquelle un logiciel offre des services à d’autres logiciels. (Wikipedia)

Spécifications ?

L’API est offerte par une bibliothèque logicielle ou un service web, le plus souvent accompagnée d’une description qui spécifie comment des programmes consommateurs peuvent se servir des fonctionnalités du programme fournisseur. (Wikipedia)

Swagger / OpenAPI

La spécification OpenAPI (OAS) définit une description d’interface standard indépendante du langage de programmation pour les API REST, qui permet aux humains et aux ordinateurs de découvrir et de comprendre les capacités d’un service sans avoir besoin d’accéder au code source, à la documentation supplémentaire ou à l’inspection du trafic réseau. (OpenAPI)

Versions

Version

Date

Notes

1.0

2011-08-10

First release of the Swagger Specification

2.0

2014-09-08

Release of Swagger 2.0

2.0

2015-12-31

Donation of Swagger 2.0 to the OpenAPI Initiative

3.0.0

2017-07-26

Release of the OpenAPI Specification 3.0.0

3.1.0

2021-02-05

Last release of the OpenAPI Specification 3.1.0

Références

Level 0 : Tester mon API via Postman

A l’époque des dinosaures…​

Étape 1 : On développe notre API 1/2

package com.sqli.pbousquet.testapi.api;

[...]

@RestController
@RequestMapping({"/api/v1/hello"})
public class HelloAPI {

    private static final Logger LOGGER = LoggerFactory.getLogger(HelloAPI.class);

    @GetMapping
    public ResponseEntity<HelloDto> hello() {
        LOGGER.info("GET /api/v1/hello");
        return hello(null);
    }

    @GetMapping(path = "/{name}")
    public ResponseEntity<HelloDto> hello(@PathVariable String name) {
        if (name != null) LOGGER.info("GET /api/v1/hello/"+name);
        HelloDto result = new HelloDto();
        result.setMessage("Hello World");
        if (name != null) {
            if ("123".equals(name)) {
                return ResponseEntity.badRequest().build();
            }
            result.setMessage("Hello " + name);
        }
        LOGGER.info("Response : "+result.getMessage());
        return ResponseEntity.ok(result);
    }
}

Étape 2 : On compile, on exécute, on teste ????

001 navigateur
  • Comment tester une API en POST, PUT ou DELETE ?

Étape 3 : Postman à la rescousse

002 postman

Problématique : Communiquer aux autres

Les consommateurs doivent connaitre :

  • Les URIs exposées

  • Les Verbes à utiliser

  • Les paramètres

  • …​

Level 1 : Tester mon API via Swagger

Offrir une UI pour tester les APIs (mais pas que)

Étape 1 : Réferencer les dépendances swagger

Fichier pom.xml :

	<dependencies>
    [...]
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<version>2.9.2</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-common</artifactId>
			<version>2.9.2</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>
			<version>2.9.2</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-bean-validators</artifactId>
			<version>2.9.2</version>
		</dependency>
	</dependencies>

Étape 2 : Configurer mon application

Fichier SwaggerConfig.java :

package com.sqli.pbousquet.testapi.config;

[...]

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    [...]

    @Bean
    public Docket api(ServletContext servletContext) {
        return new Docket(DocumentationType.SPRING_WEB)
                .pathProvider(new RelativePathProvider(servletContext) {
                    @Override
                    public String getApplicationBasePath() {
                        return basePath;
                    }
                })
                .apiInfo(DEFAULT_API_INFO)
                .produces(DEFAULT_PRODUCES_AND_CONSUMES)
                .consumes(DEFAULT_PRODUCES_AND_CONSUMES)
                .select().paths(PathSelectors.ant("/api/**")).build();
    }

}

Étape 3 : On compile, on exécute, on teste

003 swagger ui

Le petit plus : les spécifications

004 swagger specs

Level 2 : Documenter avec les Annotations

Des annotations pour de la documentation enrichie…​

Étape 1 : Modifier la conf SwaggerConfig

    @Bean
    public Docket api(ServletContext servletContext) {
        return new Docket(DocumentationType.SWAGGER_2)
                .pathProvider(new RelativePathProvider(servletContext) {
                    @Override
                    public String getApplicationBasePath() {
                        return basePath;
                    }
                })
                .apiInfo(DEFAULT_API_INFO)
                .produces(DEFAULT_PRODUCES_AND_CONSUMES)
                .consumes(DEFAULT_PRODUCES_AND_CONSUMES)
                .select().paths(PathSelectors.ant("/api/**")).build();
    }
Important
Assurez vous d’avoir sélectionné DocumentationType.SWAGGER_2

Étape 2 : Ajouter des annotation sur vos API

    @GetMapping(path = "/{name}")
    @ApiOperation(value = "Saluer une personne en particulier",
        response = HelloDto.class, position = 1)
    @ApiResponses({
            @ApiResponse(code = 200, message = "OK"),
            @ApiResponse(code = 400, message = "Mauvaise requête,
                123 n'est pas une valeurs valide")
    })
    public ResponseEntity<HelloDto> hello(@ApiParam(required = true,
        name = "name", value = "Nom de la personne à saluer")
        @PathVariable String name) {

        if (name != null) LOGGER.info("GET /api/v1/hello/"+name);
        HelloDto result = new HelloDto();
[...]
    }

Étape 3 : On compile, on exécute, on teste

005 annotations

Beaucoup d’annotations disponibles

package io.swagger.annotations.* :

  • Api

  • ApiOperation

  • ApiResponse

  • ApiParam

  • …​

Level 3 : SwaggerHub, définir et générer mon API

Pemiers pas vers la génération de code…​

Étape 1 : Décrire son API dans SwaggerHub

006 swaggerhub
Important
Ne pas utiliser cette solution en dehors de POC

Étape 2 : Générer le code (Server / Client)

007 swaggerhub gen

Étape 3 : Implémenter le code

008 ide dev

Étape 4 : On compile, on exécute, on teste

Mais solution à éviter

  • La définition des API est stockée sur le net

  • Le code généré n’est pas très gracieux

  • Mélange entre génération et implémentation manuelle

  • versions des dépendances figées dans le pom.xml

Alternative : plugin OpenAPIGenerator

Il existe un plugin pour IntelliJ :

010 ide plugin

Level 4 : Définir et générer mon API (Maven, non intrusif)

Aller plus loin dans la génération de code…​

Les premiers pas du Design First chez un Client

Plugin Maven : openapi-generator-maven-plugin

<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>4.1.0</version>
    <executions>
[...]
    </executions>
</plugin>

Étape 1 : Designer l’API

011 ide yaml

Étape 2 : Prévisualiser l’API

012 previsualiser

Étape 3 : Générer le code de l’API

<execution>
    <id>1</id>
    <goals>
        <goal>generate</goal>
    </goals>
    <configuration>
        <inputSpec>${project.basedir}/contracts/producer/hello.yaml</inputSpec>
        <generatorName>spring</generatorName>
        <library>spring-mvc</library>
        <templateDirectory>templates/producer/spring</templateDirectory>
        <generateApiTests>false</generateApiTests>
        <generateModelTests>false</generateModelTests>
        <generateSupportingFiles>true</generateSupportingFiles>
        <configOptions>
            <useTags>true</useTags>
            <sourceFolder>src/main/java</sourceFolder>
            <dateLibrary>java7</dateLibrary>
            <java8>true</java8>
            <interfaceOnly>true</interfaceOnly>
            <hideGenerationTimestamp>false</hideGenerationTimestamp>
        </configOptions>
        <apiPackage>com.sqli.pbousquet.testapi.api</apiPackage>
        <modelPackage>com.sqli.pbousquet.testapi.dto</modelPackage>
    </configuration>
</execution>

Étape 3 : Générer le code de l’API (2/2)

013 generation

Étape 4 : Implémenter le code de l’API

package com.sqli.pbousquet.testapi.api.impl;

import com.sqli.pbousquet.testapi.dto.HelloDto;
import io.swagger.annotations.Api;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Api(value = "Hello", description = "the Hello API", tags = {"hello"})
public class HelloApiImpl implements com.sqli.pbousquet.testapi.api.HelloApi {

    @Override
    public ResponseEntity<HelloDto> helloUsingGET1() {
        HelloDto result = new HelloDto();
        result.setMessage("Hello World");
        return ResponseEntity.ok(result);
    }

    @Override
    public ResponseEntity<HelloDto> helloUsingGET(String name) {
        HelloDto result = new HelloDto();
        result.setMessage("Hello "+name);
        return ResponseEntity.ok(result);
    }
}

Étape 5 : On compile, on exécute, on teste

Level 5 : Industrialisation

Génération totalement intégrée au cycle de dev…​

Étape 1 : Intégrer la génération dans le projet

Important
Ne plus passer par un projet intermédiaire

Étape 2 : Génération du code dans le cycle de dev

015 generate project

Étape 3 : On compile, on exécute, on teste

Merci !